package sk.ygor.raynor.client.test;

import java.util.List;
import java.util.Arrays;

public interface DeepComparator<T> {

    public ComparablePart comparablePart(T one, T two);

    public static interface EqualityResult {

    }

    public static final class IsEqual implements EqualityResult {

    }

    public static final class IsNotEqual implements EqualityResult {
        private final String reason;

        public IsNotEqual(String reason) {
            this.reason = reason;
        }

        @Override
        public String toString() {
            return reason;
        }
    }

    public static interface ComparablePart {
        public EqualityResult deepEquals();
    }

    public static class AlwaysEqual implements ComparablePart {

        @Override
        public EqualityResult deepEquals() {
            return new IsEqual();
        }
    }

    public static class ComparableBaseType<T> implements ComparablePart {
        private T one, two;

        public ComparableBaseType(T one, T two) {
            this.one = one;
            this.two = two;
        }

        @Override
        public EqualityResult deepEquals() {
            if (one == null) {
                return createResult(two == null);
            } else {
                return createResult(one.equals(two));
            }
        }

        public String printExpectedVsActual() {
            return "expected: " + one + ", actual: " + two;
        }

        private EqualityResult createResult(boolean cond) {
            if (cond) {
                return new IsEqual();
            } else {
                return new IsNotEqual(printExpectedVsActual());
            }
        }
    }

    public static class ComparableString extends ComparableBaseType<String> {
        public ComparableString(String one, String two) {
            super(one, two);
        }
    }

    public static class ComparableNumber extends ComparableBaseType<Number> {
        public ComparableNumber(Number one, Number two) {
            super(one, two);
        }
    }

    public static class ComparableBoolean extends ComparableBaseType<Boolean> {
        public ComparableBoolean(Boolean one, Boolean two) {
            super(one, two);
        }
    }

    public static class ComparableComposition implements ComparablePart {

        private final List<ComparablePart> parts;

        public ComparableComposition(ComparablePart... parts) {
            this.parts = Arrays.asList(parts);
        }

        @Override
        public EqualityResult deepEquals() {
            for (ComparablePart part : parts) {
                final EqualityResult partResult = part.deepEquals();
                if (partResult instanceof IsNotEqual) {
                    return partResult;
                }
            }
            return new IsEqual();
        }
    }


    static class ComparableHierarchy<T> implements ComparablePart {
        private final Class<T> rootClass;
        private final Object one;
        private final Object two;
        private final HierachyMember<? extends T>[] members;

        public ComparableHierarchy(Class<T> rootClass, Object one, Object two, HierachyMember<? extends T>... members) {
            this.rootClass = rootClass;
            this.one = one;
            this.two = two;
            this.members = members;
        }

        @Override
        public EqualityResult deepEquals() {
            if (one.getClass().equals(two.getClass())) {
                for (HierachyMember member : members) {
                    if (member.clazz.equals(one.getClass())) {
                        //noinspection unchecked
                        return member.deepComparator.comparablePart(one, two).deepEquals();
                    }
                }
                return new IsNotEqual("Not a subclass of " + rootClass.getName() + ": " +
                        one.getClass().getName()
                );
            } else {
                return new IsNotEqual("Not the same class: " +
                        one.getClass().getName() + " : " +
                        two.getClass().getName()
                );
            }
        }
    }

    public static class HierachyMember<T> {
        private final Class<T> clazz;
        private final DeepComparator<T> deepComparator;

        public HierachyMember(Class<T> clazz, DeepComparator<T> deepComparator) {
            this.clazz = clazz;
            this.deepComparator = deepComparator;
        }

    }
}


